# !/usr/bin/env python
# -*- coding:utf-8 -*-

import os
import json
import time
from datetime import datetime
import pyperclip            # 文本复制到剪贴板
from PyQt6.QtCore import Qt, QStandardPaths
from PyQt6.QtWidgets import QMainWindow, QTreeWidgetItem, QMenu, QFileDialog
from page.RS import Ui_mainWindow   # 界面
from service.tishi_Svc import Tishi     # 提示逻辑
from service.jm_Svc import Jm_add
from service.jm_first_Svc import Jm_first
from service.lr_Svc import Lr_add
from service.public_Svc import *


class Rs(QMainWindow, Ui_mainWindow):
    """主界面逻辑"""

    def __init__(self):
        super().__init__()
        self.setupUi(self)

        # 为按钮绑定事件处理函数
        self.mkliebiao_2.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)  # 监听器右键菜单
        self.mkliebiao_2.customContextMenuRequested.connect(self.show_context_jm)
        self.mkliebiao_3.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)  # Graph右键菜单
        self.mkliebiao_3.customContextMenuRequested.connect(self.show_context_lr)

        self.select_jm()
        self.ts = Tishi()  # 提示框

    def tab_clicked(self, index: int):
        """标签页切换"""
        if index == 0:  # 对应页面1的操作
            self.select_lr()    # lr
        elif index == 1:        # jm
            self.select_jm()

    def quanxuan(self, checkbox, mkliebiao, bg_count=None):
        """传入勾选框、列表，全选/取消全选"""
        is_checked = checkbox.isChecked()   # 是否选中
        if not is_checked:                          # 如果取消全选，则清空已选择
            mkliebiao.clearSelection()
            if bg_count is not None:
                bg_count.setText("")
        else:
            mkliebiao.selectAll()
            if bg_count is not None:
                bg_count.setText(f"已选报告：{mkliebiao.topLevelItemCount()}")

    def quxiao(self, checkbox, mkliebiao, bg_count=None):
        """有取消选中，则去掉全选"""
        all_items = mkliebiao.topLevelItemCount()
        selected_items = len(mkliebiao.selectedItems())
        # 如果全选状态下所有项的数量与当前选中项数量不同，则说明有项被取消选中
        if all_items != selected_items:
            checkbox.setChecked(False)  # 取消全选
        else:
            checkbox.setChecked(True)  # 全选
        if bg_count is not None:
            if selected_items:
                bg_count.setText(f"已选报告：{selected_items}")
            else:
                bg_count.setText("")

    def locate(self, tu, log, click=1):
        """
        功能：根据图片地址，获取坐标并点击
        参数：图片地址、日志模块、点击次数（1表示单击，2表示双击，0不点）
        返回：location或False
        """
        MAX_RETRIES = 2         # 重试次数
        retries = 0             # 已重试次数
        while retries < MAX_RETRIES:
            try:
                time.sleep(0.3)
                location = pg.locateCenterOnScreen(tu, confidence=0.9, grayscale=True,
                                                   region=(0, 0, move_distance_x, move_distance_y))
                # 识别图片位置grayscale=True,confidence=0.9
                pg.moveTo(location, duration=0.2)
                if click == 1:
                    pg.click(location)  # 单击
                elif click == 2:
                    pg.doubleClick(location)    # 双击
                return location                 # 成功找到并点击，返回 location
            except pg.ImageNotFoundException:
                retries += 1
                if retries == MAX_RETRIES:
                    log.append(f"经过 {retries} 次重试，仍未找到图像 {tu}。\n")
                    return False            # 最大重试次数，返回 False
                else:
                    log.append(f"第 {retries} 次尝试定位图像 {tu} 失败，将在 0.5 秒后重试。")
                    time.sleep(0.5)         # 延迟一段时间后再重试
            except pg.FailSafeException as e:
                raise e                     # 重新抛出异常，使其到外部停止循环。失效保护
            except Exception as e:
                log.append(f"定位图像 {tu} 时发生未知异常：{e}")
                return False

    def locate_coordinate(self, coordinate, click=1):
        pg.moveTo(coordinate, duration=0.2)
        if click == 1:
            pg.click(coordinate)  # 单击
        elif click == 2:
            pg.doubleClick(coordinate)  # 双击


    # ==============================================================================
    # page 1.jm页面
    # ==============================================================================

    def jm_init(self):
        """初始化识别图"""
        jf = Jm_first()
        jf.jm_first_show()  # 回显
        self.hide()  # 子窗口打开后隐藏主窗口
        jf.exec()
        self.show()  # 子窗口关闭后重新显示主窗口

    def jt_quanxuan(self):
        """全选监听器"""
        self.quanxuan(self.checkBox_jt, self.mkliebiao_2)

    def jt_quxiao(self):
        """有取消选中，则去掉全选"""
        self.quxiao(self.checkBox_jt, self.mkliebiao_2)

    def bg_quanxuan(self):
        """全选报告"""
        self.quanxuan(self.checkBox_bg, self.mkliebiao_1, self.jtq_count)

    def bg_quxiao(self):
        """有取消选中，则去掉全选"""
        self.quxiao(self.checkBox_bg, self.mkliebiao_1, self.jtq_count)

    def select_jm(self):
        """查询jm监听器"""
        self.mkliebiao_2.clear()  # 清除其所有item

        if not os.path.exists(JM_JSON):
            # 文件不存在，显示无数据
            parent_item = QTreeWidgetItem(self.mkliebiao_2)  # 创建父级项目
            parent_item.setText(0, '暂无数据')              # 设置父级项目的第一列文本（模块名称）
        else:
            with open(JM_JSON, 'r', encoding='utf-8') as f:
                data = json.load(f)
            if 'init' in data:
                del data['init']
            for value in data.keys():
                parent_item = QTreeWidgetItem(self.mkliebiao_2)  # 创建父级项目
                parent_item.setText(0, value)
        self.mkliebiao_2.show()
        self.checkBox_jt.setChecked(False)  # 取消全选

    def show_context_jm(self, pos):
        """监听器选择，右键菜单"""
        menu = QMenu(self)
        add_action = menu.addAction("新增")
        add_action.triggered.connect(lambda: self.add_jm())
        edit_action = menu.addAction("编辑")
        edit_action.triggered.connect(lambda: self.edit_jm(self.mkliebiao_2.itemAt(pos)))
        edit_action = menu.addAction("删除")
        edit_action.triggered.connect(lambda: self.delete_jm(self.mkliebiao_2.itemAt(pos)))
        menu.exec(self.mkliebiao_2.mapToGlobal(pos))

    def add_jm(self):
        """新增监听器"""
        jm = Jm_add()  # jm页面
        jm.confirmed_signal_jm.connect(lambda: (self.select_jm()))      # 关闭窗口查询
        jm.dongtai()    # 勾选，显示动态高度信息
        self.hide()  # 子窗口打开后隐藏主窗口
        jm.exec()
        self.show()  # 子窗口关闭后重新显示主窗口

    def edit_jm(self, current_item):
        """监听器编辑"""
        if current_item is not None:
            name = current_item.text(0)     # 获取当前选择的节点文本
            jm = Jm_add(name)               # jm编辑页面
            jm.edit()                       # 反显
            jm.confirmed_signal_jm.connect(lambda: (self.select_jm()))  # 保存后查询
            self.hide()  # 子窗口打开后隐藏主窗口
            jm.exec()
            self.show()  # 子窗口关闭后重新显示主窗口

    def delete_jm(self, current_item):
        """删除监听器"""
        if current_item is not None:
            name = current_item.text(0)  # 获取当前选择的节点文本
            self.ts.xinxi(f'确认删除【{name}】吗？', True)
            if self.ts.qr == 1:
                with open(JM_JSON, 'r', encoding='utf-8') as f:
                    existing_data = json.load(f)
                    value = existing_data.get(name)
                    try:
                        for i in range(1, 4):
                            file_path = value.get(f'tu{i}')             # 删除图片文件
                            if file_path is not None and os.path.exists(file_path):
                                os.remove(file_path)
                    except Exception as e:
                        self.ts.xinxi(f"识别图文件删除失败\n{e}")
                del existing_data[name]                                 # 删除记录
                with open(JM_JSON, 'w', encoding='utf-8') as f:
                    json.dump(existing_data, f)                         # 写回更新后的数据
                self.select_jm()

    def select_folder(self):
        """报告文件夹"""
        directory = os.path.normpath(QFileDialog.getExistingDirectory(self, "选择文件夹"))
        self.daoruwenjian.setText(directory)
        self.daoruwenjian.setToolTip(directory)  # 设置单元格的tooltip（鼠标悬停提示）
        self.show_folder()

    def report_folder(self):
        """选择保存路径，默认下载文件夹"""
        downloads_folder = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.DownloadLocation)
        directory = os.path.normpath(QFileDialog.getExistingDirectory(self, "选择文件夹", downloads_folder))
        self.report.setText(directory)
        self.report.setToolTip(directory)

    def show_folder(self):
        """扫描文件夹内jm报告"""
        directory = self.daoruwenjian.text()      # 获取路径
        self.file_names = {}    # 全部报告，名称：地址
        self.daochulog.clear()  # 清空说明
        self.mkliebiao_1.clear()                             # 清除其所有item
        self.jtq_count.setText("")

        for root, dirs, files in os.walk(directory, topdown=True):    # 遍历文件夹及其子文件夹中的.格式文件
            for file in files:
                if file.endswith(f'.{self.geshi.currentText()}'):                   # 条件筛选
                    file_name = os.path.basename(root) + '-' + file      # 获取文件名（含扩展名）
                    self.file_names[file_name] = os.path.join(root, file)
        count = len(self.file_names)                     # 统计报告数量

        if count == 0:
            self.daochulog.append(f'扫描完成----------没有发现.{self.geshi.currentText()}格式的文件')
        else:
            self.daochulog.append(f'扫描完成----------发现{count}个{self.geshi.currentText()}文件')
            self.select_bg()
        self.checkBox_bg.setChecked(False)  # 取消全选

    def select_bg(self):
        """显示报告列表"""
        for value in self.file_names.keys():
            parent_item = QTreeWidgetItem(self.mkliebiao_1)  # 创建父级项目
            parent_item.setText(0, value)
            parent_item.setToolTip(0, value)

    def jm_test(self):
        """识别图，点击测试"""
        jtq = [item.text(0) for item in self.mkliebiao_2.selectedItems()]   # 选中监听器
        if not jtq:
            self.ts.xinxi("请选择待测试监听器")
            return

        if activate_window("Apache JMeter"):        # jmeter启动判断
            time.sleep(1)
        else:
            self.ts.xinxi("没找到名称包含【JMeter】的窗口，请检查jmeter是否启动")
            return

        self.daochulog.append("\n识别测试----开始\n")
        pg.click(100, 600)
        pg.press('home')                                    # 切换顶部，避免按钮选中
        with open(JM_JSON, 'r', encoding='utf-8') as f:
            data = json.load(f)
        init = data.get('init', {})  # 获取tu数据
        tu1 = init.get('tu1', {})
        using_coordinates = init.get('using_coordinates1', False)    # 坐标模式
        coordinate = init.get('coordinate1', ())
        self.daochulog.append(f"初始化：----开始")

        if using_coordinates:
            if coordinate:
                self.locate_coordinate(coordinate, 1)
            else:
                self.daochulog.append("已选坐标定位，但坐标未设置，请设置坐标")
        elif tu1:
            try:
                self.locate(tu1, self.daochulog)
            except pg.FailSafeException:
                self.daochulog.append("\nPyAutoGUI失效保护触发。已停止自动化操作。")
                activate_window("Report screenshot", max_win=1)
                return
        else:
            self.daochulog.append(f"初始化：识别图1----未截取\n")

        try:
            for key in jtq:                                  # 监听器，识别图校验
                self.daochulog.append(f"{key}----开始")
                for i in range(1, 4):
                    tus = data[key].get(f'tu{i}', {})
                    if i <= 2:
                        using_coordinates_key = f'using_coordinates{i}'
                        using_coordinates = data[key].get(using_coordinates_key, False)
                        if using_coordinates:
                            coordinate_key = f'coordinate{i}'
                            coordinate = data[key].get(coordinate_key, (0,0))
                            self.locate_coordinate(coordinate, 1)
                        elif tus == {}:
                            self.daochulog.append(f"识别图{i}----未截取")
                        else:
                            self.locate(tus, self.daochulog)
                    elif tus != {}:
                        self.locate(tus, self.daochulog)
        except pg.FailSafeException:
            self.daochulog.append("\nPyAutoGUI失效保护触发。已停止自动化操作。")
        except Exception as e:
            self.daochulog.append(f"发生未知异常：{e}")
        finally:
            activate_window("Report screenshot", max_win=1)

    def jm_start(self):
        """开始截图"""
        jtq = [item.text(0) for item in self.mkliebiao_2.selectedItems()]   # 选中项目校验
        bg = [item.text(0) for item in self.mkliebiao_1.selectedItems()]
        if not jtq or not bg:
            self.ts.xinxi("请选择监听器与报告")
            return

        with open(JM_JSON, 'r', encoding='utf-8') as f:
            data = json.load(f)
        init = data.get('init', {})
        init_tu1 = init.get('tu1', {})   # 初始化，识别图校验
        init_using_coordinates = init.get('using_coordinates1', False)  # 坐标模式
        init_coordinate = init.get('coordinate1', (0,0))
        if init_using_coordinates and not init_coordinate:
            self.daochulog.append("初始化按钮，请设置坐标")
            return
        elif init_tu1 == {}:
            self.daochulog.append(f"未获取到【初始化】：识别图")
            return

        for key in jtq:                                  # 监听器，识别图校验
            for i in range(1, 4):
                tus = data[key].get(f'tu{i}', {})
                if i == 1 and data[key].get('using_coordinates1', False):
                    pass
                elif i == 2 and data[key].get('using_coordinates2', False):
                    pass
                elif i != 3 and tus == {}:
                    self.daochulog.append(f"未获取到【{key}】识别图{i}")
                    return

        if activate_window("Apache JMeter"):             # jmeter启动判断
            time.sleep(1)
        else:
            self.ts.xinxi("没找到名称包含【JMeter】的窗口，请检查jmeter是否启动")
            return

        start_time = datetime.now()
        self.daochulog.append(f"\n{start_time.strftime('%H:%M:%S')}  开始截图：\n")
        if self.report.text():                          # 截图保存文件夹
            path = self.report.text()
        else:
            # 默认下载文件夹，os.path.normpath转换路径标识符
            path = os.path.normpath(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.DownloadLocation))
        full_path = os.path.join(path, "Report diagram")
        os.makedirs(full_path, exist_ok=True)           # 新建文件夹

        pg.click(100, 600)
        pg.press('home')                                # 切换顶部，避免按钮选中
        count_bg = 0            # 计数
        count_image = 0
        try:
            for b in bg:
                self.daochulog.append(f"{b}：开始")
                if init_using_coordinates:                  # init坐标模式
                    self.locate_coordinate(init_coordinate, 1)
                else:
                    self.locate(init_tu1, self.daochulog)  # 清空监听器数据
                for key in jtq:
                    tu1 = data[key].get(f'tu1', {})     # 获取识别图
                    tu2 = data[key].get(f'tu2', {})
                    tu3 = data[key].get(f'tu3', {})
                    lisener = data[key].get(f'lisener', (0, 0, 1, 1))

                    if data[key].get('using_coordinates1', False):
                        self.locate_coordinate(data[key].get('coordinate1', (0,0)), 1)
                    else:
                        self.locate(tu1, self.daochulog)                    # 点击监听器按钮

                    if data[key].get('using_coordinates2', False):
                        self.locate_coordinate(data[key].get('coordinate1', (0,0)), 1)
                    elif self.locate(tu2, self.daochulog):                    # 点击文件输入框
                        pyperclip.copy(self.file_names[b])  # 将路径复制到剪贴板
                        pg.hotkey('ctrl', 'a')
                        pg.press('delete')
                        pg.hotkey('ctrl', 'v')  # 粘贴路径       绕过无法输入中文路径的问题
                        time.sleep(0.1)
                        pg.press('enter')
                        time.sleep(0.2)
                        pg.hotkey('ctrl', 'a')  # 清空路径，避免后续无法识别
                        pg.press('delete')
                        if tu3 != {}:  # 计算动态高度
                            l = self.locate(tu3, self.daochulog, click=0)
                            if l:
                                x, y = l
                                z = data[key].get('Offset', 0)
                                lisener[3] = int(y - lisener[1] + z)
                            else:
                                self.daochulog.append(f"【{key}】未识别到【动态高度识别图片】，无法进行下一步，请检查")
                                return
                        pg.screenshot(f"{full_path}\\{b}-{key}.png", region=lisener)  # 报告截图
                        count_image += 1
                    else:
                        self.daochulog.append(f"【{key}】未识别到【文件名输入框】，无法进行下一步，请检查")
                        return
                count_bg += 1

        except pg.FailSafeException:
            self.daochulog.append("\nPyAutoGUI失效保护触发。已停止自动化操作。")
        except Exception as e:
            self.daochulog.append(f"出错了：{e}")
        finally:
            end_time = datetime.now()
            time_duration = (end_time - start_time).total_seconds()
            minutes = int(time_duration // 60)
            seconds = int(time_duration % 60)
            self.daochulog.append(f"\n截图文件保存路径：{full_path}")
            self.daochulog.append(f"已选：报告【{len(bg)}】 X 监听器【{len(jtq)}】 = 预计截图【{len(bg)*len(jtq)}】张")
            self.daochulog.append(f"已截：报告【{count_bg}】个              图片共【{count_image}】张")
            self.daochulog.append(f"{end_time.strftime('%H:%M:%S')} 耗时：{minutes}分 {seconds}秒")
            activate_window("Report screenshot", max_win=1)

    # ==============================================================================
    # page 2.lr页面
    # ==============================================================================

    def lr_quanxuan(self):
        """全选graph"""
        self.quanxuan(self.checkBox_lr, self.mkliebiao_3)

    def lr_quxiao(self):
        """有取消选中，则去掉全选"""
        self.quxiao(self.checkBox_lr, self.mkliebiao_3)

    def lrbg_quanxuan(self):
        """全选报告"""
        self.quanxuan(self.checkBox_lrbg, self.mkliebiao_4, self.graph_count)

    def lrbg_quxiao(self):
        """有取消选中，则去掉全选"""
        self.quxiao(self.checkBox_lrbg, self.mkliebiao_4, self.graph_count)

    def select_lr(self):
        """查询Graph"""
        self.mkliebiao_3.clear()  # 清除其所有item

        if not os.path.exists(LR_JSON):
            # 文件不存在，显示无数据
            parent_item = QTreeWidgetItem(self.mkliebiao_3)  # 创建父级项目
            parent_item.setText(0, '暂无数据')              # 设置父级项目的第一列文本（模块名称）
        else:
            with open(LR_JSON, 'r', encoding='utf-8') as f:
                data = json.load(f)
            for value in data.keys():
                parent_item = QTreeWidgetItem(self.mkliebiao_3)  # 创建父级项目
                parent_item.setText(0, value)
        self.mkliebiao_3.show()
        self.checkBox_lr.setChecked(False)  # 取消全选

    def show_context_lr(self, pos):
        """监听器选择，右键菜单"""
        menu = QMenu(self)
        add_action = menu.addAction("新增")
        add_action.triggered.connect(lambda: self.add_lr())
        edit_action = menu.addAction("编辑")
        edit_action.triggered.connect(lambda: self.edit_lr(self.mkliebiao_3.itemAt(pos)))
        edit_action = menu.addAction("删除")
        edit_action.triggered.connect(lambda: self.delete_lr(self.mkliebiao_3.itemAt(pos)))
        menu.exec(self.mkliebiao_3.mapToGlobal(pos))

    def add_lr(self):
        """新增Graph"""
        lr = Lr_add()  # lr页面
        lr.confirmed_signal_lr.connect(lambda: (self.select_lr()))      # 关闭窗口查询
        lr.dongtai_lr()    # 勾选，显示动态高度信息
        self.hide()  # 子窗口打开后隐藏主窗口
        lr.exec()
        self.show()  # 子窗口关闭后重新显示主窗口

    def edit_lr(self, current_item):
        """Graph编辑"""
        if current_item is not None:
            name = current_item.text(0)     # 获取当前选择的节点文本
            lr = Lr_add(name)               # 编辑页面
            lr.edit()                       # 反显
            lr.confirmed_signal_lr.connect(lambda: (self.select_lr()))  # 保存后查询
            self.hide()  # 子窗口打开后隐藏主窗口
            lr.exec()
            self.show()  # 子窗口关闭后重新显示主窗口

    def delete_lr(self, current_item):
        """删除Graph"""
        if current_item is not None:
            name = current_item.text(0)  # 获取当前选择的节点文本
            self.ts.xinxi(f'确认删除【{name}】吗？', True)
            if self.ts.qr == 1:
                with open(LR_JSON, 'r', encoding='utf-8') as f:
                    existing_data = json.load(f)
                    value = existing_data.get(name)
                    try:
                        for i in range(1, 5):
                            file_path = value.get(f'tu{i}')             # 删除图片文件
                            if file_path is not None and os.path.exists(file_path):
                                os.remove(file_path)
                    except Exception as e:
                        self.ts.xinxi(f"识别图文件删除失败\n{e}")
                del existing_data[name]                                 # 删除记录
                with open(LR_JSON, 'w', encoding='utf-8') as f:
                    json.dump(existing_data, f)                         # 写回更新后的数据
                self.select_lr()

    def select_folder_lr(self):
        """报告文件夹"""
        directory = os.path.normpath(QFileDialog.getExistingDirectory(self, "选择文件夹"))
        self.daoruwenjian_2.setText(directory)
        self.daoruwenjian_2.setToolTip(directory)
        self.show_folder_lr()

    def report_folder_lr(self):
        """选择保存路径，默认下载文件夹"""
        downloads_folder = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.DownloadLocation)
        directory = os.path.normpath(QFileDialog.getExistingDirectory(self, "选择文件夹", downloads_folder))
        self.report_2.setText(directory)
        self.report_2.setToolTip(directory)

    def show_folder_lr(self):
        """扫描文件夹内lr报告"""
        directory = self.daoruwenjian_2.text()      # 获取路径
        self.file_names_lr = {}    # 全部报告，名称：地址
        self.daochulog_lr.clear()  # 清空说明
        self.mkliebiao_4.clear()                             # 清除其所有item
        self.graph_count.setText("")

        for root, dirs, files in os.walk(directory, topdown=True):    # 遍历文件夹及其子文件夹中的.lra格式文件
            for file in files:
                if file.endswith('.lra'):                   # 条件筛选
                    file_name = file    # os.path.basename(root) + '-' + file      # 获取文件名（含扩展名）
                    self.file_names_lr[file_name] = os.path.join(root, file)
        count = len(self.file_names_lr)                     # 统计报告数量

        if count == 0:
            self.daochulog_lr.append(f'扫描完成----------没有发现.lra格式的文件')
        else:
            self.daochulog_lr.append(f'扫描完成----------发现{count}个.lra文件')
            self.select_lrbg()
        self.checkBox_bg.setChecked(False)  # 取消全选

    def select_lrbg(self):
        """显示报告列表"""
        for value in self.file_names_lr.keys():
            parent_item = QTreeWidgetItem(self.mkliebiao_4)  # 创建父级项目
            parent_item.setText(0, value)
            parent_item.setToolTip(0, value)

    def lr_test(self):
        """lr识别图，点击测试"""
        Graph = [item.text(0) for item in self.mkliebiao_3.selectedItems()]   # 选中监听器
        if not Graph:
            self.ts.xinxi("请选择待测试Graph")
            return

        if activate_window("HP LoadRunner Analysis"):  # Analysis启动判断
            time.sleep(1)
        else:
            self.ts.xinxi("没找到名称包含【HP LoadRunner Analysis】的窗口，请检查Analysis是否启动")
            return

        self.daochulog_lr.append("\n识别测试----开始\n")
        pg.click(100, 600)
        pg.press('home')                                    # 切换顶部，避免按钮选中
        with open(LR_JSON, 'r', encoding='utf-8') as f:
            data = json.load(f)

        for key in Graph:                                  # Graph，识别图校验
            self.daochulog_lr.append(f"{key}----开始")
            for i in range(1, 5):
                tus = data[key].get(f'tu{i}', {})
                if i == 1 and tus == {}:
                    self.daochulog_lr.append(f"按钮识别图----未截取")
                elif tus != {}:
                    try:
                        if i == 1 or i == 4:
                            self.locate(tus, self.daochulog_lr)
                        elif i == 2:
                            pg.hotkey('ctrl', 'a')
                            self.locate(tus, self.daochulog_lr, click=2)
                        elif i == 3:
                            if self.locate(tus, self.daochulog_lr, click=2):
                                pg.hotkey('alt', 'f4')
                    except pg.FailSafeException:
                        self.daochulog_lr.append("\nPyAutoGUI失效保护触发。已停止自动化操作。")
        activate_window("Report screenshot", max_win=1)

    def lr_start(self):
        """开始截图"""
        graph = [item.text(0) for item in self.mkliebiao_3.selectedItems()]   # 选中项目校验
        bg = [item.text(0) for item in self.mkliebiao_4.selectedItems()]
        if not graph or not bg:
            self.ts.xinxi("请选择Graph与Analysis报告")
            return

        with open(LR_JSON, 'r', encoding='utf-8') as f:
            data = json.load(f)

        for key in graph:                                  # Graph，识别图校验
            tus = data[key].get(f'tu1', {})
            if tus == {}:
                self.daochulog_lr.append(f"未获取到【{key}】：按钮识别图")
                return

        if activate_window("HP LoadRunner Analysis"):  # Analysis启动判断
            time.sleep(1)
        else:
            self.ts.xinxi("没找到名称包含【HP LoadRunner Analysis】的窗口，请检查Analysis是否启动")
            return

        start_time = datetime.now()
        self.daochulog_lr.append(f"{start_time.strftime('%H:%M:%S')}  开始截图：\n")
        if self.report.text():                          # 截图保存文件夹
            path = self.report_2.text()
        else:
            path = os.path.normpath(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.DownloadLocation))
        full_path = os.path.join(path, "Report diagram")
        os.makedirs(full_path, exist_ok=True)           # 新建文件夹

        count_bg = 0            # 计数
        count_image = 0

        try:
            for b in bg:
                self.daochulog_lr.append(f"{b}：开始")
                pg.click(100, 600)
                pg.press('home')  # 切换顶部，避免按钮选中
                pg.hotkey('ctrl', 'o')                  # 打开报告
                time.sleep(1.5)
                pyperclip.copy(self.file_names_lr[b])   # 将路径复制到剪贴板
                pg.hotkey('ctrl', 'v')                  # 粘贴路径       绕过无法输入中文路径的问题
                pg.press('enter')
                time.sleep(4)
                pg.click(100, 600)
                pg.press('home')  # 切换顶部，避免按钮选中
                time.sleep(1)

                for key in graph:
                    tu1 = data[key].get(f'tu1', {})     # 获取识别图
                    tu2 = data[key].get(f'tu2', {})
                    tu3 = data[key].get(f'tu3', {})
                    tu4 = data[key].get(f'tu4', {})
                    lisener = data[key].get(f'lisener', (0, 0, 1, 1))
                    if not self.locate(tu1, self.daochulog_lr):                    # 点击报告按钮
                        if tu2 != {} and tu3 != {}:
                            pg.hotkey('ctrl', 'a')                  # 添加graph流程
                            self.locate(tu2, self.daochulog_lr, click=2)
                            self.locate(tu3, self.daochulog_lr, click=2)
                            pg.hotkey('alt', 'f4')
                            self.locate(tu1, self.daochulog_lr)
                        else:
                            self.daochulog_lr.append(f"{b}：未识别到按钮。无Open a New Graph识别图，无法添加按钮")

                    if tu4 != {}:                               # 计算动态高度
                        x, y = self.locate(tu4, self.daochulog_lr)
                        lisener[3] = int(y - lisener[1] - 10)
                    time.sleep(0.5)
                    pg.screenshot(f"{full_path}\\{b}-{key}.png", region=lisener)  # 报告截图

                    count_image += 1
                count_bg += 1
                pg.hotkey('ctrl', 's')      # 保存Analysis报告
                time.sleep(3)

        except pg.FailSafeException:
            self.daochulog_lr.append("\nPyAutoGUI失效保护触发。已停止自动化操作。")
        except Exception as e:
            self.daochulog_lr.append(f"出错了：{e}")
        finally:
            end_time = datetime.now()
            time_duration = (end_time - start_time).total_seconds()
            minutes = int(time_duration // 60)
            seconds = int(time_duration % 60)
            self.daochulog_lr.append(f"\n截图文件保存路径：{full_path}")
            self.daochulog_lr.append(f"已选：报告【{len(bg)}】 X Graph【{len(graph)}】 = 预计截图【{len(bg)*len(graph)}】张")
            self.daochulog_lr.append(f"已截：报告【{count_bg}】个              图片共【{count_image}】张")
            self.daochulog_lr.append(f"{end_time.strftime('%H:%M:%S')} 耗时：{minutes}分 {seconds}秒")
            activate_window("Report screenshot", max_win=1)
